package com.g001.service.ligthing;

import java.util.ArrayList;
import java.util.Random;

/**
 * A new class
 * @author Matt
 */
public class Lightning {


    public static float X_MIN = -5; //The randomized X offset of each segment
    public static float X_MAX = 5;
    public static float Y_MIN = 2; //The randomized Y offset to start the next segment (min = 0)
    public static float Y_MAX = 14;
    public static float BEND_MIN = 0.6f; //Amount the bolt 'bends' in the middle (min = 0)
    public static float BEND_MAX = 2.7f;
    public static int CHILD_COUNT_MIN = 4; //How many branches connected to the main bolt
    public static int CHILD_COUNT_MAX = 7;
    public static float CHILD_SCALE_MIN = .05f; //How much to scale each connecting branch
    public static float CHILD_SCALE_MAX = .30f;
    public static float CHILD_ANGLE_MIN = -80f; //How much to rotate the branch
    public static float CHILD_ANGLE_MAX = 80f;
    public static int RECURSIONS = 2;

    protected static final Random RND = new Random();

    public static int rnd(int low, int high) {
        if (low==high)
            return low;
        return RND.nextInt(high - low) + low;
    }

    public static float rnd(float low, float high) {
        if (low==high)
            return low;
        return low + (RND.nextFloat() * (high - low));
    }

    public static float dist(Vertex a, Vertex b) {
        float dx = b.x-a.x;
        float dy = b.y-a.y;
        return (float)Math.sqrt( dx*dx + dy*dy );
    }

    protected int recursions; //by default: only 1 set of children
    protected Bolt mainBolt;
    protected Vertex pointA=new Vertex(0, 0), pointB=new Vertex(0, 0);

    protected Lightning(int recursions) {
        this.recursions = recursions;
    }

    public Lightning() {
        this(RECURSIONS);
    }

    public void destroy() {
        mainBolt.destroy();
        mainBolt = null;
    }

    public boolean generated() {
        return mainBolt!=null;
    }

    public Bolt getMainBolt() {
        return mainBolt;
    }

    public Vertex getPointA() {
        return pointA;
    }

    public Vertex getPointB() {
        return pointB;
    }

    protected Bolt createBolt(float height) {
        return new Bolt(height);
    }

    public void generate(float x1, float y1, float x2, float y2) {
        generate(new Vertex(x1, y1), new Vertex(x2, y2));
        //get the height of the first bolt
    }

    public void generate(Vertex pointA, Vertex pointB) {
        this.pointA = pointA;
        this.pointB = pointB;
        float height = dist(pointA, pointB);
        mainBolt = createBolt(height);
        mainBolt.generate(recursions);
    }

    public static class Bolt {

        protected float x, y, angle;

        protected float height;
        protected float xMin, xMax, yMin, yMax;
        protected float bendMin, bendMax;
        protected ArrayList<Vertex> vertices;
        protected int childCountMin, childCountMax;
        protected float childScaleMin, childScaleMax, childAngleMin, childAngleMax;

        protected ArrayList<Bolt> children;
        private int remainingRecursions;
        private Bolt parent;

        protected Bolt(float height, float xMin, float xMax, float yMin,
                float yMax, float bendMin, float bendMax, int childCountMin,
                int childCountMax, float childScaleMin, float childScaleMax,
                float childAngleMin, float childAngleMax) {
            this.height = height;
            this.xMin = xMin;
            this.xMax = xMax;
            this.yMin = yMin;
            this.yMax = yMax;
            this.bendMin = bendMin;
            this.bendMax = bendMax;
            this.childCountMin = childCountMin;
            this.childCountMax = childCountMax;
            this.childScaleMin = childScaleMin;
            this.childScaleMax = childScaleMax;
            this.childAngleMin = childAngleMin;
            this.childAngleMax = childAngleMax;
        }

        Bolt(Bolt parent, float height, float x, float y, float angle) {
            this(height, parent.xMin, parent.xMax, parent.yMin, parent.yMax,
                    parent.bendMin, parent.bendMax, parent.childCountMin,
                    parent.childCountMax, parent.childScaleMin, parent.childScaleMax,
                    parent.childAngleMin, parent.childAngleMax);
            this.parent = parent;
            this.x = x;
            this.y = y;
            this.angle = angle;
        }

        /** The master bolt. */
        protected Bolt(float height) {
            this(height, X_MIN, X_MAX, Y_MIN, Y_MAX, BEND_MIN, BEND_MAX,
                    CHILD_COUNT_MIN, CHILD_COUNT_MAX, CHILD_SCALE_MIN,
                    CHILD_SCALE_MAX, CHILD_ANGLE_MIN, CHILD_ANGLE_MAX);
        }

        public int children() {
            return children==null ? 0 : children.size();
        }

        public Bolt getChild(int index) {
            return children==null ? null : children.get(index);
        }

        public void destroy() {
            vertices = null;
            if (children!=null) {
                for (Bolt b : children)
                    b.destroy();
            }
            children = null;
        }

        public Vertex getRandomVertex() {
            return vertices==null ? null : vertices.get(RND.nextInt(vertices.size()));
        }

        public Vertex getVertex(int index) {
            return vertices==null ? null : vertices.get(index);
        }

        public int vertices() {
            return vertices==null ? 0 : vertices.size();
        }

        public void trimToSize() {
            if (vertices!=null)
                vertices.trimToSize();
            if (children!=null) {
                children.trimToSize();
                for (Bolt b : children)
                    b.trimToSize();
            }
        }

        /** Refreshes this bolt; no children will be added. */
        public void generate() {
            generate(0);
        }

        public void generate(int remainingRecursions) {
            vertices = new ArrayList<Vertex>((int)(height/Math.max(1, yMin)));

            //GENERATE THE MAIN BOLT

            //how much bend to apply in the middle, and in which direction
            float drift = Math.max(0, rnd(bendMin, bendMax)) * (RND.nextBoolean() ? -1 : 1);

            float x=0, y=0;
            if (height>0)
                vertices.add(new Vertex(0, 0));
            //for each 'segment' (line)
            while (y < height) {
                //don't offset X initially, so that branches line up
                if (y > 1) {
                    float mult = y >= (height/2f) ? -1 : 1;
                    x = x + (mult*drift) + rnd(xMin, xMax); //x offset for new points
                }
                y += Math.max(0, rnd(yMin, yMax)); //Y offset for new points
                if (y>=height) {
                    break; //height is reached; end the loop
                } else {
                    vertices.add(new Vertex(x, y));
                }
            }

            //GENERATE CHILD BOLTS
            if (remainingRecursions>0) {
                int childCount = rnd(childCountMin, childCountMax);
                children = new ArrayList<Bolt>(childCount);
                for (int i=0; i<childCount; i++) {
                    float childHeight = rnd(childScaleMin, childScaleMax) * height;
                    float childAngle = rnd(childAngleMin, childAngleMax);
                    Vertex childOrigin = getRandomVertex();
                    //create the child...
                    Bolt b = new Bolt(Bolt.this, childHeight, childOrigin.x,
                            childOrigin.y, childAngle);
                    children.add(b);
                    b.generate(remainingRecursions - 1);
                }
            }

        }
    }

    public static class Vertex {
        protected float x, y;

        public Vertex(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }
}
